
package interfaz;

import classfileanalyzer.Main;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

/**
 *
 * @author Dorya/Emmanuel
 */
public class Clase {
    
    private String nombre;
    private int totalAtributos;
    private int totalMetodos;
    private Class superClase;
    private String nombrePaquete;
    private ArrayList<Class> Clases;
    private ArrayList<File> Fuente;
    private int claseSeleccionada;
    private ArrayList<String> incompilables;
    private String direccion;


    
    
    public Clase(){
        
      
            
        incompilables = new ArrayList<>();
        Clases = new ArrayList<>();
        Fuente = new ArrayList<>();
        File directorioByteCode = new File("dptemp");
        directorioByteCode.mkdir();
                
//        leerPaquete();

    }
    
    /**
     * Lee el paquete mostrando un selector de archivos.
     */
    public final void leerPaquete(){
    
        JFileChooser selector;
        String nomPaquete;
        
        selector = new JFileChooser(); 
        selector.setCurrentDirectory(new java.io.File("."));
        selector.setDialogTitle("Seleccionar paquete");
        selector.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);//Solo directorios, no importan los archivos sueltos.
    
        
        selector.setAcceptAllFileFilterUsed(false);
      
        if (selector.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { 
            direccion = selector.getSelectedFile().getAbsolutePath(); 
            nomPaquete = selector.getSelectedFile().getName();
            this.nombrePaquete = nomPaquete;
            String archivos;
            File folder = new File(direccion);
            File[] listaArchivos = folder.listFiles(); 
            URL url = null;
            URL[] urls = null;
            ClassLoader cl = null;
            String nomPackage = null;
            try{
                 url = selector.getSelectedFile().toURI().toURL();   
                 urls = new URL[]{url};
                 cl = new URLClassLoader(urls);
            }catch(Exception e){
                e.printStackTrace();
            }
            
            //Por cada uno de los archivos en ese paquete lo procesara
            //y cargara en un arreglo.
            for (int i = 0; i < listaArchivos.length; i++) {

                if (listaArchivos[i].isFile()) {
                    archivos = listaArchivos[i].getName();
                    
                //Unicamente archivos terminados en .java
                if (archivos.endsWith(".java") || archivos.endsWith(".JAVA")) {


                try {
                    
                    Fuente.add(listaArchivos[i]);
                    compilar(listaArchivos[i].getAbsolutePath());

                    //Extrae el nombre del paquete del codigo fuente
                    BufferedReader lector = 
                           new BufferedReader(new FileReader(listaArchivos[i]));
                    String linea = lector.readLine();
                    while(linea != null){
                        if(linea.matches("package [A-Za-z0-9_]+[/.]?[[A-Za-z0-9_][/.]?]*[;]")){
                            nomPackage = linea.split("package ")[1];
                            break;
                        }
                        linea = lector.readLine();
                    }

                    //Si no se encontro entonces nomas que compile el archivo.java
                    if(nomPackage == null){
                        nomPackage = archivos.split(".java")[0];
                    }
                    else{
                        nomPackage = nomPackage.replace(";", "");
                        nomPackage += "." + archivos.split(".java")[0];
                    }

                    Clases.add(cl.loadClass(nomPackage));


                }
                catch(Exception e){
                    incompilables.add(listaArchivos[i].getName());
                }

                }
            }
         }
        }
    }
    
    /**
     * Regresa verdadero si una clase es interfaz o si es abstracta
     * @param clase
     * @return 
     */
    public boolean esInterfaz(String clase){
        
        boolean es = false;
        for(Class c: Clases)
            if(c.getSimpleName().contentEquals(clase))
                if(c.isInterface() || Modifier.isAbstract(c.getModifiers()))
                    es = true;
        return es;
    }
    
    /**
     * Devuelve la clase segun su nombre, si no devuelve nulo
     * @param nombre
     * @return 
     */
    public Class clasePorNombre(String nombre){
        
        Class clase = null;
        for(Class c: Clases){
            if(c.getSimpleName().equalsIgnoreCase(nombre)){
                clase = c;
                break;
            }
        }
        
        return clase;
    }

    /**
     * Dado el nombre de una clase, encuentra si ésta clase es una subclase.
     * @param nomClase Nombre de la supuesta superclase.
     * @return Si es o no una subclase.
     */
    public boolean esUn(String clase1, String clase2) {
       
        int numeroDeClase1 = 0;
        int numeroDeClase2 = 0;

        for(Class c: Clases){
            if(c.getSimpleName().contentEquals(clase1.trim())){
                break;
            }
            numeroDeClase1++;
        }
        
        for(Class c: Clases){
            if(c.getSimpleName().contentEquals(clase2.trim())){
                break;
            }
            numeroDeClase2++;
        }

            return Clases.get(numeroDeClase1).isInstance(Clases.get(numeroDeClase2));
       
            
        }
  
    
    /**
     * Encuentra si ésta clase tiene algún atributo.
     * @param nomAtributo Nombre del atributo a buscar.
     * @return Si tiene o no atributo.
     */
    public boolean tieneUn(String clase,String nomAtributo){
        
        int numeroDeClase1 = 0;
        boolean tiene = false;
        for(Class c: Clases){
            if(c.getSimpleName().equalsIgnoreCase(clase)){
                break;
            }
            numeroDeClase1++;
        }
        
        for(Field campo: Clases.get(numeroDeClase1).getDeclaredFields()){
            if(campo.getType().getSimpleName().equalsIgnoreCase(nomAtributo)){
//                System.out.println(clase + ":" + nomAtributo);
                tiene = true;
                
            }
            else if(campo.getGenericType().toString().contains("<"+nomAtributo+">")){
//                System.out.println("Generico "+clase + ":" + nomAtributo);
                tiene = true;
            }
            
            
        }
        
        return tiene;
    }
    
    
    
    public boolean tieneUnGenerico(String clase,String nomAtributo){
        
        int numeroDeClase1 = 0;
        boolean tiene = false;
        for(Class c: Clases){
            if(c.getSimpleName().equalsIgnoreCase(clase)){
                break;
            }
            numeroDeClase1++;
        }
        
        for(Field campo: Clases.get(numeroDeClase1).getDeclaredFields()){
//            System.out.println(nomAtributo + ":" + campo.getGenericType().toString());
            if(campo.getGenericType().toString().contains(nomAtributo+">")){
                tiene = true;
            }
        }
        
        return tiene;
    }
    /**
     * Encuentra si algún método devuelve el tipo según el nombre.
     * @param tipo Nombre del tipo.
     * @return Si devuelve o no el tipo.
     */
    public boolean algunMetodoDevuelve(String clase, String tipo){
        
        int numeroDeClase1 = 0;

        for(Class c: Clases){
            if(c.getSimpleName().equalsIgnoreCase(clase)){
                break;
            }
            numeroDeClase1++;
        }
        
        for(Method metodo : Clases.get(numeroDeClase1).getMethods())
            if(metodo.getReturnType().getSimpleName().equalsIgnoreCase(tipo))
                return true;
        
        return false;
    }
    
    /**
     * Devuelve el modificador de acceso del constructor.
     * @return 
     */
    public boolean constructorPrivado(String clase){
        int numeroDeClase1 = 0;

        for(Class c: Clases){
            if(c.getSimpleName().equalsIgnoreCase(clase)){
                break;
            }
            numeroDeClase1++;
        }
        
//        boolean privado = false;
        try{
        if(Modifier.isPrivate(Clases.get(numeroDeClase1).getDeclaredConstructors()[0].getModifiers()))
                return true;
        else
            return false;
        }catch(Exception e){
            return false;
        }
        
//        for(Constructor m: Clases.get(numeroDeClase1).getDeclaredConstructors()){
//            if(Modifier.isPrivate(m.getModifiers()))
//                privado = true;
//            
//        }
//        return privado;
        
    }
    
    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getNombrePaquete() {
        return nombrePaquete;
    }

    public void setNombrePaquete(String nombrePaquete) {
        this.nombrePaquete = nombrePaquete;
    }

    public Class getSuperClase(String simpleName) {
        return superClase;
    }

    public void setSuperClase(Class superClase) {
        this.superClase = superClase;
    }

    public int getTotalAtributos() {
        return totalAtributos;
    }

    public void setTotalAtributos(int totalAtributos) {
        this.totalAtributos = totalAtributos;
    }

    public int getTotalMetodos() {
        return totalMetodos;
    }

    public void setTotalMetodos(int totalMetodos) {
        this.totalMetodos = totalMetodos;
    }

    public ArrayList<Class> getClases() {
        return Clases;
    }
    
    public ArrayList<String> getIncompilables() {
        return incompilables;
    }    

    public ArrayList<File> getFuente() {
        return Fuente;
    }

    public int getClaseSeleccionada() {
        return claseSeleccionada;
    }
    
    
    public String compilar(String nomClase) throws Exception{
            JavaCompiler compilador = ToolProvider.getSystemJavaCompiler();
            int resultado = compilador.run(null, null, null, nomClase);
            
		if(resultado != 0)
			throw new Exception("Archivo incompilable");
      return nomClase + ".class";
    }

    public boolean existeClase(String nombreSimple){
        
        boolean existe = false;
        if(Clases == null)
            return false;
        for(Class c1: this.Clases){
            if(c1.getSimpleName().equalsIgnoreCase(nombreSimple))
                existe = true;
        }
        
        return existe;
        
    }
    public boolean hayAsociacion(String clase1, String clase2){
        
        
        int numeroDeClase1 = 0;
        boolean tiene = false;
        for(Class c: Clases){
            if(c.getSimpleName().equalsIgnoreCase(clase1)){
                break;
            }
            numeroDeClase1++;
        }
        
        if(Clases.get(numeroDeClase1).getSuperclass() != null){
            for(Field campo: Clases.get(numeroDeClase1).getSuperclass().getDeclaredFields()){
                if(campo.getType().getSimpleName().equalsIgnoreCase(clase2))
                    return true;
                }
            
        }
        for(Field campo: Clases.get(numeroDeClase1).getDeclaredFields()){
            if(campo.getType().getSimpleName().equalsIgnoreCase(clase2))
                return true;
        }
        
        for(Method metodo: Clases.get(numeroDeClase1).getDeclaredMethods()){
            if(metodo.getReturnType().getSimpleName().equalsIgnoreCase(clase2))
                return true;
            
            for(Class tipo: metodo.getParameterTypes()){
                if(tipo.getSimpleName().equalsIgnoreCase(clase2))
                    return true;
            }
            
        }
        
        return tiene;
//        return hay;
        
    }
    
    public boolean hayAsociacion(String clase1, String clase2, boolean bytecode){
          
        String ruta = "";
        for(File f: Fuente){
            if(f.getName().equalsIgnoreCase(clase1 + ".java")){
                ruta = f.getAbsolutePath();
                ruta = ruta.replace(clase1 + ".java", clase1 + ".class");
                break;
            }
        }
        
        
            File archivo1 = null;
            File archivo = null;
            try{
                    archivo1 = new File("dptemp/" + clase1 + ".j");

                    if(!archivo1.exists())
                        Main.main(new String[]{"-file" , ruta});

                    archivo = new File("dptemp/"+ clase1 + ".j");
            }catch(Exception aex){
                JOptionPane.showConfirmDialog(null, ruta);
            }

                try{ 



                    BufferedReader lector = null;
                        lector = new BufferedReader(new FileReader(archivo));

                    String linea = lector.readLine();

                    while(linea != null){
                        if((linea.contains("new") && linea.contains(clase2))
                        || (linea.contains(".method") && linea.contains(clase2))
                        || (linea.contains(".field") && linea.contains(clase2))
                        || (linea.contains(".signature") && linea.contains(clase2))){
                            return true;
                            
                        }

                        linea = lector.readLine();
                    }
                }catch(IOException e){

                    e.printStackTrace();
                }
                
                return false;
    }
    
   
    
    public void borrarClass(){
        ExtensionFilter ef = new ExtensionFilter(".class");
        
        for(File f: new File(direccion).listFiles(ef)){
            f.delete();
        }
        
    }
    
    class ExtensionFilter implements FilenameFilter {

     private String extension;

     public ExtensionFilter( String extension ) {
       this.extension = extension;             
     }
    @Override
     public boolean accept(File dir, String name) {
       return (name.endsWith(extension));
     }
   }
    
}
